﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Web.UI;
using System.Web.UI.HtmlControls;

namespace MyLib.Web {
	/// <summary>
	/// 
	/// </summary>
	public abstract class MyPage : System.Web.UI.Page, IDbContext {
		/// <summary>
		/// 
		/// </summary>
		private const int DefaultAuthorityApplicationId = 1;

		/// <summary>
		/// 
		/// </summary>
		protected log4net.ILog m_logger;

		/// <summary>
		/// 
		/// </summary>
		private HttpHandlerResourceManager<MyPage> m_httpResource;

		/// <summary>
		/// 
		/// </summary>
		private int m_authorityApplicationId;

		/// <summary>
		/// 
		/// </summary>
		internal IDictionary<string, Control> m_cachedDescendantControls;

		#region Event

		/// <summary>
		/// 
		/// </summary>
		public event EventHandler PreLoadResources;

		/// <summary>
		/// 
		/// </summary>
		protected virtual void OnPreLoadResources() {
			if (PreLoadResources != null) {
				PreLoadResources(this, EventArgs.Empty);
			}
		}

		#endregion

		/// <summary>
		/// 
		/// </summary>
		private void CheckSpecialParameter() {
			var ma = Request.Params["__my_action"];
			if (ma == null || ma.Length == 0) {
				return;
			}

			switch (ma) {
				case "get_mylib_version":
					Response.Clear();
					var htmlContent = @"
<!doctype html>
<html><head><title>MyLib version</title></head><body>MyLib Version: {0}</body>";
					var myLibVersion = typeof(MyLib.Session).Assembly.GetName().Version;
					Response.Write(string.Format(htmlContent, myLibVersion));
					Response.End();
					break;
				default:
					break;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		private void AddScriptsForCompatibility() {
			// IE 6-8
			var flag = false;
			if (Request.Browser.IsBrowser("IE")) {
				var vs = Request.Browser.Version;
				if (!string.IsNullOrWhiteSpace(vs)) {
					var ver = 0D;
					if (double.TryParse(vs, out ver)) {
						flag = ver < 9D;
					}
				}
			}

			if (flag) {
				HtmlHead head = null;

				if (this.Master != null) {
					head = Master.Controls.OfType<HtmlHead>().FirstOrDefault();
				} else {
					head = Controls.OfType<HtmlHead>().FirstOrDefault();
				}

				if (head != null) {
					var shifScript = new HtmlGenericControl("script");
					shifScript.Attributes.Add("type", "text/javascript");
					shifScript.Attributes.Add("src", ResolveClientUrl("~/Scripts/html5shiv.js"));
					shifScript.InnerHtml = "&nbsp;";

					head.Controls.Add(shifScript);
				}
			}
		}

		/// <summary>
		/// 
		/// </summary>
		protected MyPage() {
			m_logger = log4net.LogManager.GetLogger(GetType());

			m_cachedDescendantControls = new Dictionary<string, Control>();
		}

		/// <summary>
		/// 
		/// </summary>
		protected internal HttpHandlerResourceManager<MyPage> HttpResource {
			get {
				return m_httpResource;
			}
		}

		/// <summary>
		/// 
		/// </summary>
		[Obsolete("Use security.xml instead.")]
		protected virtual int[] EssentialActions {
			get {
				return new int[0];
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="e"></param>
		protected override void OnInit(EventArgs e) {
			base.OnInit(e);

			m_httpResource = new HttpHandlerResourceManager<MyPage>(this, Context);
			m_authorityApplicationId = DefaultAuthorityApplicationId;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="e"></param>
		protected override void OnLoad(EventArgs e) {
			// Check special parameter.
			CheckSpecialParameter();

			// Check authority.
			var secProvider = new MyLib.Data.SecurityFileProvider(
				Context.Request.MapPath(@"~/security.xml"),
				new WebCacheProvider(Context));
			CheckAuthoriy(Context, 
				secProvider.FindActionsByName(Context.Request.AppRelativeCurrentExecutionFilePath).ToArray());

			OnPreLoadResources();

			// Local constants.
			const string AltValidatorScriptKey = "AltValidator";

			const string LocalizationScriptKey = "LocalizationScriptKey";

			// Keep only ONE validator when multiple validators is not valid.
			if (!ClientScript.IsStartupScriptRegistered(AltValidatorScriptKey)) {
				ClientScript.RegisterStartupScript(
					typeof(Page),
					AltValidatorScriptKey,
@"
// Replace validator.
if (typeof(ValidatorUpdateDisplay) !== 'undefined') {
	var old_ValidatorUpdateDisplay = ValidatorUpdateDisplay;

	ValidatorUpdateDisplay = function(val) {
		for (var i = 0, v; i < Page_Validators.length && (v = Page_Validators[i]) != val; i++) {
			if (v.controltovalidate == val.controltovalidate && !v.isvalid)
				val.isvalid = true;
		}
		old_ValidatorUpdateDisplay(val);
	};
}
", true);
			}

			// Add localization string script.
			if (!ClientScript.IsStartupScriptRegistered(LocalizationScriptKey)) {
				ClientScript.RegisterStartupScript(
					typeof(Page),
					LocalizationScriptKey,
					string.Format(
@"
// Localization.
my_setUpdateMessage(""{0}"");
",
					Properties.Main.Loading),
					true);
			}

			// Add javascripts to ScriptManager.
			var sm = ScriptManager.GetCurrent(this);
			if (sm == null) {
				m_logger.WarnFormat("Cannot find ScriptManager in this page.");
			} else {
				sm.Scripts.Insert(0, new ScriptReference("MyLib.Web.jquery.js", typeof(MyPage).Assembly.FullName));
				sm.Scripts.Insert(1, new ScriptReference("MyLib.Web.My.js", typeof(MyPage).Assembly.FullName));
			}

			// Add compatible scripts.
			AddScriptsForCompatibility();

			base.OnLoad(e);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="e"></param>
		protected override void OnPreRenderComplete(EventArgs e) {
			base.OnPreRenderComplete(e);

			// Append sub title to master.
			MyMasterPage mp = null;
			var p = Master;
			while (p != null) {
				if (p is MyMasterPage) {
					mp = (MyMasterPage)p;
					break;
				} else {
					p = p.Master;
				}
			}

			if (mp != null) {
				mp.SubTitle = Title;
			}

			// Output messages to hidden field.
			const string ShowMessagesKey = "ShowMessages";

			var es = string.Concat("my_responseErrors = [ ",
				string.Join(", ", Context.GetErrors().Select(s => "\"" + s.EncodeJsString() + "\"")),
				" ];\r\n");
			var ws = string.Concat("my_responseWarns = [ ",
				string.Join(", ", Context.GetWarns().Select(s => "\"" + s.EncodeJsString() + "\"")),
				" ];\r\n");
			var ifs = string.Concat("my_responseInformations = [ ",
				string.Join(", ", Context.GetInformations().Select(s => "\"" + s.EncodeJsString() + "\"")),
				" ];\r\n");

			var hms = "my_handleMessages();";

			ScriptManager.RegisterStartupScript(
				this,
				typeof(Page),
				ShowMessagesKey,
				string.Concat(
@"
/************* START SCRIPTS BEGIN ***************/
",
@"
// Set messages.
",
				es, ws, ifs,
@"
// Handle messages.
",
				hms,
@"

/************* START SCRIPTS END ***************/

"
				),
				true);
		}

		/// <summary>
		/// 
		/// </summary>
		private static byte[] SerializeObject(object obj) {
			if (obj == null)
				return null;

			using (var ms = new MemoryStream()) {
				var formatter = new BinaryFormatter();
				formatter.Serialize(ms, obj);
				return ms.ToArray();
			}
		}

		/// <summary>
		/// 
		/// </summary>
		private static object DeserializeObject(byte[] bytes) {
			object obj = null;
			if (bytes == null)
				return obj;
			using (var ms = new MemoryStream(bytes)) {
				var formatter = new BinaryFormatter();
				return formatter.Deserialize(ms);
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns></returns>
		protected override object LoadPageStateFromPersistenceMedium() {
#if PROFILE
			var s0 = new System.Diagnostics.Stopwatch();
			s0.Start();
#endif
			var viewStateServerUrl = System.Configuration.ConfigurationManager.AppSettings["MyLib.ViewStateServer"];
			object ret = null;
			if (!string.IsNullOrWhiteSpace(viewStateServerUrl)) {
				var persister = Activator.GetObject(typeof(MyLib.Web.ViewStatePersister), viewStateServerUrl)
					as MyLib.Web.ViewStatePersister;
				var viewStateId = Convert.ToInt64(
					((System.Web.UI.Pair)base.LoadPageStateFromPersistenceMedium()).Second);
				ret = DeserializeObject(persister.Load(viewStateId));
			} else {

				ret = base.LoadPageStateFromPersistenceMedium();
			}

#if PROFILE
			s0.Stop();
			m_logger.DebugFormat("Loading page state cost {0} millseconds.", s0.ElapsedMilliseconds);
#endif
			return ret;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="state"></param>
		protected override void SavePageStateToPersistenceMedium(object state) {
#if PROFILE
			var s0 = new System.Diagnostics.Stopwatch();
			s0.Start();
#endif
			var viewStateServerUrl = System.Configuration.ConfigurationManager.AppSettings["MyLib.ViewStateServer"];
			if (!string.IsNullOrWhiteSpace(viewStateServerUrl)) {
				var persister = Activator.GetObject(typeof(MyLib.Web.ViewStatePersister), viewStateServerUrl)
					as MyLib.Web.ViewStatePersister;
				var viewStateId = persister.Save(SerializeObject(state));
				base.SavePageStateToPersistenceMedium(viewStateId);
			} else {
				base.SavePageStateToPersistenceMedium(state);
			}

#if PROFILE
			s0.Stop();
			m_logger.DebugFormat("Loading page state cost {0} millseconds.", s0.ElapsedMilliseconds);
#endif
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="e"></param>
		protected override void OnUnload(EventArgs e) {
			base.OnUnload(e);

			if (m_httpResource != null) {
				m_httpResource.Close();
			}
		}

		#region IDbContext

		SessionFactory IDbContext.SessionFactory {
			get {
				return m_httpResource.GetSessionFactory();
			}
		}

		#endregion

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="actionIdArray"></param>
		/// <returns></returns>
		protected bool IsAuthorized(System.Web.HttpContext context, params int[] actionIdArray) {
			if (actionIdArray == null || actionIdArray.Length == 0) {
				return true;
			}

			// TODO: ApplicationId should be read.
			if (context.User == null || context.User.Identity == null || string.IsNullOrEmpty(context.User.Identity.Name)) {
				return false;
			} else if (!GetDbProvider<MyLib.Data.AuthorityProvider>().IsAuthorized(m_authorityApplicationId, context.User.Identity.Name, actionIdArray)) {
				return false;
			} else {
				return true;
			}
		}

#pragma warning disable 0618
		/// <summary>
		/// 
		/// </summary>
		/// <param name="context"></param>
		/// <param name="actionIdArray"></param>
		protected void CheckAuthoriy(System.Web.HttpContext context, params int[] actionIdArray) {
			if (!IsAuthorized(context, actionIdArray)) {
				throw new AccessDeniedHttpException();
			}
		}
#pragma warning restore 0618

		/// <summary>
		/// 
		/// </summary>
		protected void CheckValidators() {
			if (!Page.IsValid) {
				throw new InvalidOperationException("Page is not valid.");
			}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <typeparam name="T"></typeparam>
		/// <returns></returns>
		protected T GetDbProvider<T>() where T : DbContextProvider, new() {
			return (T)m_httpResource.GetDbProvider(typeof(T));
		}
	} // end of MyPage.
}
